package com.curlyben.net
{
	import com.curlyben.events.HTTPEvent;
	import com.curlyben.utils.Console;
	import com.curlyben.utils.Strings;
	import com.dynamicflash.util.Base64;
	import flash.display.Sprite;
	import flash.net.*;
	import flash.events.*;
	import flash.system.ApplicationDomain;
	import flash.system.Security;
	import flash.errors.EOFError;
	import flash.utils.*;
	import mx.utils.StringUtil;


	dynamic public class HTTPRequest extends EventDispatcher
	{
				
		// events
		public static const ER_MALFORMEDURL:String		= "MalformedUrlError";
		
		//class attributes
		public var Port:uint = 80;
		public var BaseStream:URLStream;
		private var pRequestPacket:Object = new Object();
		public static var RedHerringURL:String = "[none]";
		public var Request:URLRequest;
		public var RequestMethodOverride:String;
		public var Resource:String;
		public var Host:String;
		
		public static const HTTP_VER_1_0:String = "1.0";
		public static const HTTP_VER_1_1:String = "1.1";
		public var HTTPVersion:String = HTTP_VER_1_0;
		public var IsSSL:Boolean = false;
		
		public function get RequestData():Object {
			return Request.data;
		}
		public function set RequestData(d:Object):void {
			Request.data = d;
		}
		
		public function get ResponseData():ByteArray {
			if (pHeaderComplete)
				return Buffer;
			return new ByteArray();
		}
		public var ResponseWord:String;
		public var ResponseCode:String;
		public var ResponseDescription:String;
		public var ResponseHeaders:Array;
		
		private var pBytesLoaded:uint;
		private var pBytesTotal:uint;
		
		private var pHeaderComplete:Boolean;
		private var pDataComplete:Boolean;
		private var pBuffer:ByteArray;
		private var pRawHeader:String;
		public function get RawHeader():String {
			return pRawHeader;
		}
		
		public function get Buffer():ByteArray {
			var b:ByteArray = new ByteArray();
			var pbp:uint = pBuffer.position;
			pBuffer.position = 0;
			b.writeBytes(pBuffer, 0, pBuffer.length);
			b.position = 0;
			pBuffer.position = pbp;
			return b;
		}
			
		public function HTTPRequest(redherring:String = "redherring.php")
		{
			if(RedHerringURL == "[none]") RedHerringURL = redherring;
			BaseStream = new URLStream();	
			BaseStream.addEventListener(Event.OPEN, eConnected);
			BaseStream.addEventListener(Event.COMPLETE, eSocketClosed);
			BaseStream.addEventListener(ProgressEvent.PROGRESS, onSocketDataEvent);
			BaseStream.addEventListener(ProgressEvent.SOCKET_DATA, onSocketDataEvent);
			BaseStream.addEventListener(IOErrorEvent.IO_ERROR, onIOErrorEvent);
			BaseStream.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityErrorEvent);
		}
	
		public function Load(request:URLRequest, requestdata:Object = null, forcemethod:String = null):void {
			pHeaderComplete = false;
			pDataComplete = false;
			pBuffer = new ByteArray();
			Request = request;
			RequestData = requestdata;
			if (forcemethod)
				RequestMethodOverride = forcemethod;
			
			if(pParseURL()){
				pSendRequest();
			}
			else
				dispatchEvent(new Event(ER_MALFORMEDURL));
		}
		
		public function Close():void {
			if (!BaseStream.connected)
				return;
			BaseStream.close();
			dispatchEvent(new Event(Event.CLOSE));
		}

		private function eConnected(event:Event):void {	
			Console.Trace("Connected...");
			dispatchEvent(new HTTPEvent(HTTPEvent.EV_CONNECTED, this));
		}
			
		private function eSocketClosed(event:Event):void
		{
			Console.Trace("Disconnected.");
			if (pRawHeader == null)
				dispatchEvent(new HTTPEvent(HTTPEvent.EV_PREMATUREDISCONNECT, this));
			else
				dispatchEvent(new HTTPEvent(HTTPEvent.EV_DATACOMPLETE, this));
		}
			
		private function onIOErrorEvent(event:IOErrorEvent):void
		{ 
			dispatchEvent(event);
		}
		
		private function onSecurityErrorEvent(event:SecurityErrorEvent):void
		{
			Console.Trace("Socket Security Error: " + event.type);
			Console.Trace("Switching to RedHerring.");
		}
		
		private function onSocketDataEvent(event:ProgressEvent):void 
		{
			//pBuffer.writeUTFBytes(BaseStream.readUTFBytes(BaseStream.bytesAvailable));
			pBuffer.position = pBuffer.length;
			while (BaseStream.bytesAvailable) { pBuffer.writeByte(BaseStream.readByte()); }
			
			while(true){
				if (!pHeaderComplete) {
					var rx:RegExp;
					pBuffer.position = 0;
					var header:String = pBuffer.readUTFBytes(pBuffer.bytesAvailable);
					pBuffer.position = pBuffer.length - 1;
					if (header.indexOf("\r\n\r\n") < -1)
						break;
					header = header.substr(0, header.indexOf("\r\n\r\n"));
					pRawHeader = header;
					
					rx = /([a-z0-9\x2E\x2F\x5C]*)(?:\s)(\d*)(?:\s)(.*\r)/mi;
					var match_stat:* = rx.exec(header);
					ResponseWord		= match_stat[1];
					ResponseCode 		= match_stat[2];
					ResponseDescription = match_stat[3];
					
					rx = /^([a-z0-9\x2D\x5F]*)(?:\x3A)(.*)$/gmi;
					var match_header:* = rx.exec(header);
					while (match_header !== null) {
						ResponseHeaders.push(new HTTPHeader(match_header[1], match_header[2]));
						if (match_header[1].toLowerCase() == "content-length")
							pBytesTotal = parseInt(match_header[2]);
						match_header = rx.exec(header);
					}
					
					dispatchEvent(new HTTPStatusEvent(HTTPStatusEvent.HTTP_STATUS, false, false, parseInt(ResponseCode)));
					dispatchEvent(new HTTPEvent(HTTPEvent.EV_HEADERCOMPLETE, this));
						
					pHeaderComplete = true;
					
					var tBuffer:ByteArray = new ByteArray();
					pBuffer.position = header.length + 4; // TODO: may crash if header incomplete/content = null 
					while (pBuffer.bytesAvailable > 0)
						tBuffer.writeByte(pBuffer.readByte());
					pBuffer = tBuffer;
				}
				break;
			}
			
			if (pHeaderComplete) {
				dispatchEvent(new HTTPEvent(HTTPEvent.EV_DATAPROGRESS, this));
			}
		}
	
		
		private function pSendRequest():void {
			pBytesLoaded 		= 0;
			pBytesTotal 		= 0;
			ResponseCode 		= "";
			ResponseDescription = "";
			ResponseHeaders 	= new Array();
			ResponseWord 		= "";
			pBuffer 			= new ByteArray();
			pDataComplete 		= false;
			pHeaderComplete 	= false;
			var buffer:String = "";
			buffer += (RequestMethodOverride !== null ? RequestMethodOverride : Request.method) + " " + Resource + " " + (IsSSL ? "HTTPS" : "HTTP") + "/" + HTTPVersion + "\r\n";
			buffer += "Host: " + Host + "\r\n";
			if (RequestData)
				buffer += "Content-Length: " + (RequestData as String).length + "\r\n";
			for each(var h:* in Request.requestHeaders)
				if (h.name.length > 0 && h.name.toLowerCase() !== "host" && h.name.toLowerCase() !== "content-length")
					buffer += h.name + ": " + h.value + "\r\n";
			
			pRequestPacket.host = Host;
			pRequestPacket.port = Port.toString();
			pRequestPacket.headers = Strings.TrimEnd(buffer, "\r\n");	
			pRequestPacket.data = (RequestData as String);
			

			var urlr:URLRequest = new URLRequest(RedHerringURL);
			urlr.method = 	URLRequestMethod.POST;
			urlr.data 	= 	Base64.encode(pRequestPacket.host) + ">" +
							Base64.encode(pRequestPacket.port) + ">" +
							Base64.encode(pRequestPacket.headers) + ">" +
							Base64.encode(pRequestPacket.data);
			BaseStream.load(urlr);
			Console.Trace("Connecting...");
		}

		private function pParseURL():Boolean {
			var rx:RegExp = /(?:http[s]?\x3A\x2F\x2F)([a-z0-9\x2E\x2D]+)(?:\x3A*)(\d*)(?:\x2F*)(.*)/gi;
			var match:* = rx.exec(Request.url);
			if (!match) 
				return false;
			/*for (var i:* in match)
				trace(i, match[i]);*/
			Port 		= match[2].length > 0 ? parseInt(match[2]) : Request.url.indexOf("https") > -1 ? 443 : 80;
			Host 		= match[1];
			Resource 	= match[3].length > 0 ? "/" + match[3] : "/" ;
			IsSSL = Request.url.toLowerCase().indexOf("https") > -1;
			return true;
		}
		
		public function GetHeader(name:String):HTTPHeader {
			for each(var h:HTTPHeader in ResponseHeaders)
				if (name.toLowerCase() == h.Name.toLowerCase())
					return h;
			return HTTPHeader.Make(name);
		}
	}

}
